package com.stark.commons.spring.core.boot.config;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.transaction.TransactionProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import com.stark.commons.spring.core.boot.properties.TransactionAopProperties;

import lombok.extern.slf4j.Slf4j;

/**
 * 事务切面自动配置。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0
 */
@Configuration
@ConditionalOnClass(PlatformTransactionManager.class)
@ConditionalOnProperty(prefix = "spring.transaction.aop", name = "base-packages")
@EnableConfigurationProperties(TransactionAopProperties.class)
@Slf4j
public class TransactionAopAutoConfig {
	
	@Autowired
	private TransactionAopProperties transactionAopProperties;
	
	@Autowired(required = false)
	private TransactionProperties transactionProperties;
	
	@Autowired
	private PlatformTransactionManager transactionManager;
	
	@Bean
	public Advisor txAdvisor() {
		String expression = createExpression(transactionAopProperties.getBasePackages());
		
		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
		pointcut.setExpression(expression);
		Advisor txAdvisor = new DefaultPointcutAdvisor(pointcut, txAdvice());
		
		log.info("自动配置事务切面：properties={},expression={}", transactionAopProperties, expression);
		
		return txAdvisor;
	}
	
	private String createExpression(String basePackages) {
		StringBuffer sb = new StringBuffer();
		String[] pkgs = basePackages.split(",");
		if (pkgs.length == 1) {
			sb.append(execution(pkgs[0]));
		} else {
			sb.append("(");
			for (String pkg : pkgs) {
				sb.append(sb.length() == 1 ? "" : " || ").append(execution(pkg));
			}
			sb.append(")");
		}
		sb.append(" && !@annotation(org.springframework.transaction.annotation.Transactional)")
			.append(" && !@within(org.springframework.transaction.annotation.Transactional)")
			.append(" && !@annotation(com.stark.commons.spring.core.annotation.NonTransactional)")
			.append(" && !@within(com.stark.commons.spring.core.annotation.NonTransactional)");
		String expression = sb.toString();
		return expression;
	}
	
	private TransactionInterceptor txAdvice() {
		// 只读事务
		RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
		readOnlyRule.setReadOnly(true);
		readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
		// 写事务
		RuleBasedTransactionAttribute requiredRule = new RuleBasedTransactionAttribute();
		requiredRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(RuntimeException.class)));
		requiredRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		requiredRule.setTimeout(getTimeout());
		
		// 配置只读事务
		Map<String, TransactionAttribute> transactionAttributeMap = new HashMap<>();
		if (StringUtils.isNotBlank(transactionAopProperties.getReadOnlyMethods())) {
			for (String method : transactionAopProperties.getReadOnlyMethods().split(",")) {
				transactionAttributeMap.put(method, readOnlyRule);
			}
		}
		// 配置写事务
		for (String method : transactionAopProperties.getRequiredMethods().split(",")) {
			transactionAttributeMap.put(method, requiredRule);
		}
			
		NameMatchTransactionAttributeSource attributeSource = new NameMatchTransactionAttributeSource();
		attributeSource.setNameMap(transactionAttributeMap);
		TransactionInterceptor txAdvice = new TransactionInterceptor((TransactionManager) transactionManager, attributeSource);
		return txAdvice;
	}
	
	private String execution(String pkg) {
		return new StringBuffer()
				.append("execution (* ").append(pkg).append("..*(..))")
				.toString();
	}
	
	private Integer getTimeout() {
		if (transactionAopProperties.getTimeout().intValue() > -1) {
			return transactionAopProperties.getTimeout();
		}
		if (transactionProperties != null
				&& transactionProperties.getDefaultTimeout() != null
				&& transactionProperties.getDefaultTimeout().getSeconds() > -1) {
			return Integer.valueOf(String.valueOf(transactionProperties.getDefaultTimeout().getSeconds()));
		}
		return -1;
	}
	
}
